/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// network.cpp - network communication layer
//


#include "common.h"


/*
 ==============================================================================

 LOOPBACK BUFFERS FOR LOCAL PLAYER

 ==============================================================================
*/

#define	MAX_LOOPBACK			4

struct loopPacket_t {
	byte				data[MAX_PACKET_SIZE];
	int					size;
};

struct loopback_t {
	int					get;
	int					send;

	loopPacket_t		packets[MAX_LOOPBACK];
};

static loopback_t		net_loopbacks[2];


/*
 ==================
 NET_GetLoopPacket
 ==================
*/
bool NET_GetLoopPacket (netSrc_t sock, netAdr_t *from, void *data, int *size, int maxSize){

	loopback_t		*loop;
	loopPacket_t	*packet;

	loop = &net_loopbacks[sock];

	if (loop->send - loop->get > MAX_LOOPBACK)
		loop->get = loop->send - MAX_LOOPBACK;

	if (loop->get >= loop->send)
		return false;

	packet = &loop->packets[loop->get & (MAX_LOOPBACK - 1)];
	loop->get++;

	if (packet->size > maxSize)
		Com_Error(true, "NET_GetLoopPacket: overflow");

	Mem_Copy(data, packet->data, packet->size);
	*size = packet->size;

	Mem_Fill(from, 0, sizeof(netAdr_t));
	from->type = NA_LOOPBACK;

	return true;
}

/*
 ==================
 NET_SendLoopPacket
 ==================
*/
void NET_SendLoopPacket (netSrc_t sock, const netAdr_t to, const void *data, int size){

	loopback_t		*loop;
	loopPacket_t	*packet;

	if (to.type != NA_LOOPBACK)
		Com_Error(true, "NET_SendLoopPacket: bad address type");

	loop = &net_loopbacks[sock ^ 1];

	packet = &loop->packets[loop->send & (MAX_LOOPBACK - 1)];
	loop->send++;

	if (size > MAX_PACKET_SIZE)
		Com_Error(true, "NET_SendLoopPacket: overflow");

	Mem_Copy(packet->data, data, size);
	packet->size = size;
}


/*
 ==============================================================================

 OUT-OF-BAND DATAGRAMS

 ==============================================================================
*/


/*
 ==================
 NET_OutOfBandData
 ==================
*/
void NET_OutOfBandData (netSrc_t sock, const netAdr_t to, const void *data, int size){

	byte	buffer[MAX_PACKET_SIZE];

	if (size + sizeof(int) > MAX_PACKET_SIZE)
		Com_Error(true, "NET_OutOfBandData: overflow");

	// Write the sequence
	*(int *)buffer = OOB_SEQUENCE;

	// Write the data
	Mem_Copy(buffer + sizeof(int), data, size);

	// Send the datagram
	if (to.type == NA_LOOPBACK)
		NET_SendLoopPacket(sock, to, buffer, size + sizeof(int));
	else
		NET_SendPacket(sock, to, buffer, size + sizeof(int));
}

/*
 ==================
 NET_OutOfBandPrintf
 ==================
*/
void NET_OutOfBandPrintf (netSrc_t sock, const netAdr_t to, const char *fmt, ...){

	char	string[MAX_PACKET_SIZE];
	va_list	argPtr;
	int		len;

	// Write the sequence
	*(int *)string = OOB_SEQUENCE;

	// Write the string
	va_start(argPtr, fmt);
	len = Str_VSPrintf(string + sizeof(int), MAX_STRING_LENGTH, fmt, argPtr);
	va_end(argPtr);

	if (len == -1)
		Com_Error(true, "NET_OutOfBandPrintf: overflow");

	// Send the datagram
	if (to.type == NA_LOOPBACK)
		NET_SendLoopPacket(sock, to, string, len + 1 + sizeof(int));
	else
		NET_SendPacket(sock, to, string, len + 1 + sizeof(int));
}


// ============================================================================


/*
 ==================
 NET_IsLocalAddress
 ==================
*/
bool NET_IsLocalAddress (const netAdr_t adr){

	if (adr.type == NA_LOOPBACK)
		return true;

	return false;
}

/*
 ==================
 NET_IsLANAddress
 ==================
*/
bool NET_IsLANAddress (const netAdr_t adr){

	if (adr.type == NA_LOOPBACK)
		return true;

	if (adr.type != NA_IP)
		return false;

	// Special case for loopback (127.0.0.1)
	if (adr.ip[0] == 127 && adr.ip[1] == 0 && adr.ip[2] == 0 && adr.ip[3] == 1)
		return true;

	// RFC 1918 class A block (10.0.0.0 to 10.255.255.255)
	if (adr.ip[0] == 10)
		return true;

	// RFC 1918 class B blocks (172.16.0.0 to 172.31.255.255)
	if (adr.ip[0] == 172 && (adr.ip[1] >= 16 && adr.ip[1] <= 31))
		return true;

	// RFC 1918 class C blocks (192.168.0.0 to 192.168.255.255)
	if (adr.ip[0] == 192 && adr.ip[1] == 168)
		return true;

	return false;
}

/*
 ==================
 NET_CompareAddress
 ==================
*/
bool NET_CompareAddress (const netAdr_t adr1, const netAdr_t adr2){

	if (adr1.type != adr2.type)
		return false;

	if (adr1.type == NA_LOOPBACK)
		return true;

	if (adr1.type == NA_IP){
		if (adr1.ip[0] == adr2.ip[0] && adr1.ip[1] == adr2.ip[1] && adr1.ip[2] == adr2.ip[2] && adr1.ip[3] == adr2.ip[3] && adr1.port == adr2.port)
			return true;

		return false;
	}

	return false;
}

/*
 ==================
 NET_CompareBaseAddress
 ==================
*/
bool NET_CompareBaseAddress (const netAdr_t adr1, const netAdr_t adr2){

	if (adr1.type != adr2.type)
		return false;

	if (adr1.type == NA_LOOPBACK)
		return true;

	if (adr1.type == NA_IP){
		if (adr1.ip[0] == adr2.ip[0] && adr1.ip[1] == adr2.ip[1] && adr1.ip[2] == adr2.ip[2] && adr1.ip[3] == adr2.ip[3])
			return true;

		return false;
	}

	return false;
}